package com.soup.hummingbird.module.spring;

import com.soup.hummingbird.module.AbstractCollect;
import com.soup.hummingbird.module.Collect;
import com.soup.hummingbird.module.AgentLoader;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

/**
 * 〈对于spring的controller方法进行插桩增强〉<br>
 *
 * @author zhaoyi
 * @date 2021-04-05 22:18
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class SpringControllerCollect extends AbstractCollect implements Collect {

    public static SpringControllerCollect INSTANCE = new SpringControllerCollect();
    // 方法执行前的增强
    private static final String beginSrc;
    // 方法执行结束的增强
    private static final String endSrc;
    // 方法执行发生异常的增强
    private static final String errorSrc;

    private String rootRequestUrl = "";

    static {
        // 前置插桩，声明SpringControllerCollect的实例 instance，声明统计对象WebStatistics的实例statistic（通过调用begin方法的返回值赋值）
        StringBuilder sbuilder = new StringBuilder();
        sbuilder.append("com.soup.hummingbird.module.spring.SpringControllerCollect instance = com.soup.hummingbird.module.spring.SpringControllerCollect.INSTANCE;\r\n");
        sbuilder.append("com.soup.hummingbird.module.spring.SpringControllerCollect.WebStatistics statistic = (com.soup.hummingbird.module.spring.SpringControllerCollect.WebStatistics) instance.begin(\"%s\",\"%s\");\r\n");
        sbuilder.append("statistic.urlAddress=\"%s\";\r\n");
        beginSrc = sbuilder.toString();

        // 这里方法执行结束后的增强是调用父类的 end方法，将异常堆栈发送给
        sbuilder = new StringBuilder();
        sbuilder.append("instance.end(statistic);");
        endSrc = sbuilder.toString();

        // 这里方法执行发生异常的增强是调用父类的 error方法，将异常堆栈发送给
        sbuilder = new StringBuilder();
        sbuilder.append("instance.error(statistic,e);");
        errorSrc = sbuilder.toString();
    }

    /**
     * 发送统计信息实现
     * @param stat 统计信息
     */
    @Override
    public void sendStatistics(Statistics stat) {
        sendStatisticByHttp(stat,"webLog");
    }

    /**
     * 判断是否是目标
     * @param className 类名称
     * @param loader 类加载器
     * @param ctclass 类元信息
     * @return 是否插桩目标：true 是 | false 否
     */
    @Override
    public boolean isCollectTarget(String className, ClassLoader loader, CtClass ctclass) {
        boolean result = false;
        try {
            for (Object obj : ctclass.getAnnotations()) {
                // 通过正则表达示计算出RequestMapping 地址
                if (obj.toString().startsWith("@org.springframework.web.bind.annotation.RequestMapping")) {
                    // 获取controller类上的@RequestMapping注解的值，作为根请求url值
                    rootRequestUrl = getAnnotationValue("value", obj.toString());
                } else if (obj.toString().startsWith("@org.springframework.stereotype.Controller")) {
                    result = true;
                } else if (obj.toString().startsWith("@org.springframework.web.bind.annotation.RestController")) {
                    result = true;
                }
            }
        } catch (ClassNotFoundException e) {
            System.err.println(String.format("bit apm run error targetClassName=%s errorMessage=%s",className,e.getClass().getSimpleName()+":"+e.getMessage()));
        }
        return result;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, byte[] classfileBuffer, CtClass ctclass) throws Exception {
        AgentLoader byteLoader = new AgentLoader(className, loader, ctclass);
        CtMethod[] methods = ctclass.getDeclaredMethods();
        for (CtMethod method : methods) {
            // 屏蔽非公共方法
            if (!Modifier.isPublic(method.getModifiers())) {
                continue;
            }
            // 屏蔽静态方法
            if (!Modifier.isStatic(method.getModifiers())) {
                continue;
            }
            // 屏蔽本地方法
            if (!Modifier.isNative(method.getModifiers())) {
                continue;
            }

            String requestUrl;
            // 需要带上RequestMapping
            if (null == (requestUrl = getRequestMappingValue(method))) {
                continue;
            }

            AgentLoader.MethodSrcBuild build = new AgentLoader.MethodSrcBuild();
            // 将前置插桩、后置插桩、异常插桩的源码占位符替换
            // beginSrc有3个占位符，这里分别进行替换
            build.setBeginSrc(String.format(beginSrc, className, method.getName(), rootRequestUrl + requestUrl));
            build.setEndSrc(endSrc);
            build.setErrorSrc(errorSrc);

            // 替换方法
            byteLoader.updateMethod(method, build);
        }

        return byteLoader.toBytecode();
    }

    /**
     * 重写父类的begin方法，返回扩展的统计类WebStatistics实例，该类扩展了一些统计信息
     * @param className 全限定类名
     * @param method 方法名
     * @return 统计信息实例对象
     */
    @Override
    public Statistics begin(String className, String method) {
        WebStatistics webStat = new WebStatistics(super.begin(className, method));
        webStat.controlName = className;
        webStat.methodName = method;
        webStat.logType = "web";
        return webStat;
    }

    /**
     * 获取方法注解上的url
     * @param m 方法元信息
     * @return controller url映射
     * @throws ClassNotFoundException 异常
     */
    private String getRequestMappingValue(CtMethod m) throws ClassNotFoundException {
        for (Object s : m.getAnnotations()) {
            if (s.toString().startsWith("@org.springframework.web.bind.annotation.RequestMapping")) {
                String val = getAnnotationValue("value", s.toString());
                return null == val  ? "/" : val;
            } else if (s.toString().startsWith("@org.springframework.web.bind.annotation.GetMapping")) {
                String val = getAnnotationValue("value", s.toString());
                return null == val  ? "/" : val;
            } else if (s.toString().startsWith("@org.springframework.web.bind.annotation.PostMapping")) {
                String val = getAnnotationValue("value", s.toString());
                return null == val  ? "/" : val;
            } else if (s.toString().startsWith("@org.springframework.web.bind.annotation.PutMapping")) {
                String val = getAnnotationValue("value", s.toString());
                return null == val  ? "/" : val;
            } else if (s.toString().startsWith("@org.springframework.web.bind.annotation.DeleteMapping")) {
                String val = getAnnotationValue("value", s.toString());
                return null == val  ? "/" : val;
            }
        }
        return null;
    }

    /**
     * 收集信息类继承父类的Statistics类，增加了一些扩展字段
     */
    public static class WebStatistics extends Statistics {
        // url 地址，前置插桩代码中进行了赋值操作
        public String urlAddress;
        // 服务名称
        public String controlName;
        // 方法名称
        public String methodName;

        public WebStatistics(Statistics s) {
            super(s);
        }
    }
}
